https://alistapart.com
Most pages on the web contain a menu of links in a navigation area. These are often marked up as a string of links, often in separate DIVs or paragraphs. Structurally, however, they are a list of links, and should be marked up as such.
Of course the reason that we don’t mark them up in that way is that we don’t want a bullet in front of every link in our navigation area. In a previous article I outlined several techniques for using CSS to layout a web page. One of those techniques involved manipulating a list to display horizontally rather than vertically.
In this article, I’ll demonstrate how to use CSS to bring unwieldy lists under control. It’s time for you to tell lists how to behave, instead of letting them run wild on your web page.
For purposes of this article, I am using unordered lists. The same CSS can be applied, with similar results, to ordered lists as well. Unless otherwise defined, all of the examples in this article use the following code for the lists.
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5 we'll make a bit longer so
that it will wrap</li>
</ul>
Each list is simply placed inside a different DIV, and the CSS is written so that the list’s behavior is determined by the DIV it is in. Each DIV has a base rule:
#base { border: 1px solid #000; margin: 2em; width: 10em; padding: 5px; }
Without any additional styles applied, the list is rendered in this way in the base DIV:
Sometimes the default indent of a list is too much for the design you are working on. But simply changing the margin or the padding of the UL doesn’t work for all browsers. To make the list flush left, e.g., you need to change both the margin and the padding. This is because Internet Explorer and Opera opted to create the indent with the left margin, while Mozilla/Netscape use padding. For more on this see the DevEdge article Consistent List Indentation.
In the following example both the margin-left
and padding-left
of the UL in the DIV are set to zero:
Note that the markers lie outside of the DIV. If the containing box were the BODY of the HTML document, the markers might be rendered outside of the browser window, in effect vanishing. If you want the markers to line up inside the DIV, but along its left side, set either the left padding or margin to one em:
Maybe you’ve had a project that required a custom bullet. If so you might have marked it up in a table with one column containing the GIF image bullets aligned top and right, and the other column containing the content of what should have been LIs. With CSS it is possible to use an image as a bullet. If the browser doesn’t support this part of CSS (or doesn’t do images), the default bullet will be used (or you can specify a different HTML bullet if you wish).
The rule looks something like this:
ul { list-style-image: url(bullet.gif); }
And the browser renders it:
To specify an HTML bullet to use if the browser doesn’t support this part of CSS, add:
list-style-type: disc;
to your rule. Depending on the image that you choose you may find that it doesn’t align itself with the list items the way that you intend. In that case you may choose to have the image be placed within the list item block (rather than outside the block). Adding the following:
list-style-position: inside;
to your rule will make that change. These three declarations can be combined into a single, shorthand declaration as illustrated in the following rule:
ul { list-style: disc url(bullet.gif) inside; }
which is the equivalent of:
ul { list-style-type: disc; list-style-image: url(bullet.gif); list-style-position: inside; }
This is what it looks like in the web page:
There may be times when you have a list, but you don’t want any bullets, or you want to use some other character in place of the bullet. Again, CSS provides a straightforward solution. Simply add list-style: none;
to your rule and force the LIs to display with hanging indents. The rule will look something like this:
ul { list-style: none; margin-left: 0; padding-left: 1em; text-indent: -1em; }
Either the padding or the margin needs to be set to zero, with the other one set to 1em. Depending on the “bullet” that you choose, you may need to modify this value. The negative text-indent
causes the first line to be moved to the left by that amount, creating a hanging indent.
The HTML will contain our standard UL, but with whatever character or HTML entity that you want to use in place of the bullet preceding the content of the list item. In our case we’ll be using »
, the right double angle quote: ».
I should point out that Netscape6/7/Mozilla (and other Gecko-based browsers) and Opera can create generated content via the CSS2 :before
pseudo-element. We can take advantage of this to automatically generate the » character (or any other character) for the bullets. This allows us to leave the content of the UL alone. If you are using Opera or a Gecko-based browser, the following CSS will create the same list as above, but using the standard UL with no extra content:
#custom-gen ul li:before { content: "\0BB \020"; }
The content
property may contain strings, URIs and more, including special characters. When using these characters, like », it is necessary to encode them as their escaped HEX equivalents. For the right double angle quote, we use \0BB (the other character, \020, is a space). The final result (remember, the character will only be visible in Opera or Mozilla/Netscape):
Who says a list needs to be vertically aligned with bullets hanging off the left side of each item? Perhaps you want the structure of an ordered list of links, but visually you want it to look like a vertical navigation bar on the web page. Or maybe you want your list of links to align themselves horizontally across the top of your page.
This applies to more than just lists of links. There are times when you might need to list several items in the midst of a paragraph, maybe a list of books that you want to read. Structurally it makes sense to mark this up as a list, but presentationally you might not want break the flow of the paragraph. CSS to the rescue again!
Since this list will not be separate and unto itself, I won’t put it into the base DIV that the previous lists have inhabited. This time the markup will be a paragraph, followed by the same list, followed by another paragraph.
I hear you crying,“FOUL! I thought you were going to put a list inside of a paragraph, not between two paragraphs.”
To which I reply,“Well, yes. But (X)HTML does not allow a list to appear inside of a paragraph. However, with the help of our style sheet, that is how it will look in the web page.”
Here’s what the styles look like:
#inline-list { border: 1px solid #000; margin: 2em; width: 80%; padding: 5px; font-family: Verdana, sans-serif; }#inline-list p { display: inline; }#inline-list ul, #inline-list li { display: inline; margin: 0; padding: 0; color: #339; font-weight: bold; }
The markup consists of a <div id=“inline-list”>
. This DIV contains a paragraph followed by our standard UL, and a followup paragraph. The UL list has been modified so that each list item has a comma after it, with the last item followed by a period.
The results are below (list appears bold and blue):
A bit of text before the list appears. Perhaps the context is something about a husband getting a call from his wife to pick up a few things on the way home from work. It doesn’t really matter, for our purposes we just need some preceding text before the list:
And then there is the text that follows the list in the paragraph. One or two sentences is sufficient for the example.
As in the custom bullet example above, we could use CSS to automatically generate the commas and period that follow each list item. If all you had to worry about were Opera and Gecko powered browsers, that is. This time the styles would look like:
#inline-list-gen ul li:after { content: ", "; } #inline-list-gen ul li.last:after { content: ". "; }
Here we use the :after
pseudo-element to add a comma after each list item, and a period after a list item with class=“last”
, resulting in the following (remember, it will only be visible in Opera or Mozilla/Netscape):
A bit of text before the list appears. Perhaps the context is something about a husband getting a call from his wife to pick up a few things on the way home from work. It doesn’t really matter, for our purposes we just need some preceding text before the list:
And then there is the text that follows the list in the paragraph. One or two sentences is sufficient for the example.
As I mentioned previously, the menus of links that appear on nearly every site should really be marked up as lists, since that is what they are. Since we usually don’t want the default list style to apply to these links, we can use CSS to change the way they appear on the page. As we saw above, lists can be forced to display horizontally (inline) rather than stacked vertically (the default behavior). When you do this the bullet goes away and you have many choices about how to separate the list items.
These examples of horizontal lists will all use the same base DIV with the following styles:
#h-contain { padding: 5px; border: 1px solid #000; margin-bottom: 25px; }
The next two examples use the same UL as in the previous examples, but without the final list item with its extra text. They also include an additional class that sets apart one of the LIs in the list.
A pipe character, |, is often used to differentiate between choices. It is an obvious separating character, and can be emulated by adding borders to list items:
#pipe ul { margin-left: 0; padding-left: 0; display: inline; } #pipe ul li { margin-left: 0; padding: 3px 15px; border-left: 1px solid #000; list-style: none; display: inline; } #pipe ul li.first { margin-left: 0; border-left: none; list-style: none; display: inline; }
Here we add class=“first”
to the first LI so that it does not end up with a border on its left side.
You can modify these styles to create a tabbed navigation effect:
#tabs ul { margin-left: 0; padding-left: 0; display: inline; } #tabs ul li { margin-left: 0; margin-bottom: 0; padding: 2px 15px 5px; border: 1px solid #000; list-style: none; display: inline; } #tabs ul li.here { border-bottom: 1px solid #ffc; list-style: none; display: inline; }
In this example adding class=“here”
to an LI creates a bottom border that matches the background color to indicate that the tab refers to the current page.
Note: This technique was first proffered by Randal Rust, and then riffed on by many on the css-discuss email list
Another list of links that typically has a horizontal orientation on a web page is what has become known as breadcrumbing. Breadcrumbs show you where you are in the hierarchy of a site, starting with the home page and drilling down to the current section or page. If you really want to make the markup meaningful, you would want to create a series of nested lists, since each new section is part of the section before it:
<div id="bread"> <ul> <li class="first">Home <ul> <li>» Products <ul> <li>» Computers <ul> <li>» Software</li> </ul></li> </ul></li> </ul></li> </ul> </div>
creates the following:
Adding the following rules to the style sheet for the page:
#bread { color: #ccc; background-color: #006; padding: 3px; margin-bottom: 25px; }#bread ul { margin-left: 0; padding-left: 0; display: inline; border: none; } #bread ul li { margin-left: 0; padding-left: 2px; border: none; list-style: none; display: inline; }
creates this:
Again, we can generate the » character (or any other character you might want to use as a separator) with the :before
pseudo-element, combined with a class=“first” so that the first LI doesn’t generate a separator:
#bread-gen ul li:before { content: "\020 \020 \020 \0BB \020"; color: #ff9; } #bread-gen ul li.first:before { content: " "; }
And the end result:
I’d like to end with a real world application of some of the techniques that have been discussed here. We’ll use a standard UL containing links to create a dynamic menu with hover effects. In order to obtain the hover effects we’ll let the UL provide the structure, and the anchor styles will provide most of the visual effects.
This menu of links is actually a solution to a question posed by Michael Efford on the css-discuss list. Michael had created this exact effect using a table, images, and JavaScript. He asked the list if it could be done with CSS. I took the challenge, and with the help of several other members who tracked down some browser specific issues, we came up with a style sheet that works on this markup:
<div id="button"> <ul> <li><a href="#">Home</a></li> <li><a href="#">Hidden Cameras</a></li> <li><a href="#">CCTV Cameras</a></li> <li><a href="#">Employee Theft</a></li> <li><a href="#">Helpful Hints</a></li> <li><a href="#">F.A.Q</a></li> <li><a href="#">About Us</a></li> <li><a href="#">Contact Us</a></li> </ul> </div>
Let’s look at the style sheet rule by rule, and I’ll explain why each rule is constructed the way that it is.
#button { width: 12em; border-right: 1px solid #000; padding: 0 0 1em 0; margin-bottom: 1em; font-family: 'Trebuchet MS', 'Lucida Grande', Verdana, Lucida, Geneva, Helvetica, Arial, sans-serif; background-color: #90bade; color: #333; }
The first rule is for the #button
DIV. It defines the space that the menu will occupy, and provides a context for the menu so that we can define the way the list and links will behave inside the DIV. I chose to make the menu fluid, based on the browser’s font size preferences, so (almost) all units are in ems. This includes the width of the menu. The solid black border on the right was based on the original design from Michael. The bottom padding is there to extend the DIV down beyond the menu of links so that you can see the background of the DIV. Again, this follows the original design. The bottom margin is to separate the DIV from what follows it. The colors came from the original design.
#button ul { list-style: none; margin: 0; padding: 0; border: none; } #button li { border-bottom: 1px solid #90bade; margin: 0; }
Next I defined what the list will look like. Since all the list items were to be links, and the rollover functionality would be built into the CSS for the links, I essentially removed all styling from the lists. I did add a single pixel border on the bottom of each link that matched the background of the #button
DIV, to work as the separator. In the original design, this was an image.
#button li a { display: block; padding: 5px 5px 5px 0.5em; border-left: 10px solid #1958b7; border-right: 10px solid #508fc4; background-color: #2175bc; color: #fff; text-decoration: none; width: 100%; } html>body #button li a { width: auto; } #button li a:hover { border-left: 10px solid #1c64d1; border-right: 10px solid #5ba3e0; background-color: #2586d7; color: #fff; }
Finally, I defined the links. The original design has 10 pixel borders on the right and left. These borders, along with the background, change color on the rollover. This is a relatively simple thing to control with the :hover
pseudo-class in CSS, so I put this style into the anchor styles.
There is one workaround in this part of the style sheet. To make the links active for the full width of the DIV, I made them display: block;
. This works for everything but IE/Windows. If you give the block an explicit width of 100%, then IE/Windows plays along. But doing this creates problems with IE5/Mac and Netscape/Mozilla. So I used the child selector“>” to redefine the width to auto. Since IE/Windows doesn’t understand child selectors, it ignores the rule. IE5/Mac, Opera and Netscape/Mozilla follow the rule, and everyone is happy.
The rule for the :hover
pseudo-class creates the color changes on the backgrounds and borders when the links are moused over.
The style and list markup (about 1K worth) replaced about 5K of JavaScript and TABLE markup, not to mention another 8K or so of images for the rollover effects. It also made the markup more readable, and easier to update, since you no longer need to create new images if a link name changes. Now you simply modify some text. You can view the final result in the context of the site on the Asset Surveillance web site.
Believe it or not, we have just scratched the surface of what can be done to modify lists with style sheets. I won’t claim that all of the techniques presented here are the most practical CSS that you’ll come across, but I do hope they make you think about how using CSS can release you to use more structured markup.
The Web Standards Project’s (WaSP) Browser Upgrade Initiative (BUI), has spurred many a designer to move towards more standards compliant web design, using CSS rather than tables for layout to save user bandwidth while enhancing underlying semantics, accessibility, and reach.
Several designers have taken Jeffrey Zeldman’s lead in posting tutorials that have helped us get over the initial hump of table-less design. The first efforts have focused on creating two or more columns using CSS positioning instead of tables, thus allowing for a (complete) separation of structure from presentation. These broader techniques have been documented and archived at Eric Costello’s glish and Rob Chandanais’ Blue Robot.
Others have chimed in, including Owen Briggs’ Box lesson and Tantek Çelik’s box model hack/workaround, detailed by Eric Costello, and explained by Tantek.
While these excellent resources address the larger issue of creating a general layout using only CSS positioning, other practical questions arise as we find ourselves, as designers, faced with a problem that is trivially easy to solve with tables, but not so obvious with CSS. Such a question was posed on the Webdesign-L list with the subject line “Tables are dead ... long live tables.”
Suppose you want to have a bunch of thumbnail images that link to larger versions of the images – a fairly common type of web page. Suppose further that each image contains a short caption that you would like to keep centered underneath its image. And, in the interest of conserving browser window real estate, you want to line these image/caption pairs up in rows across the screen, but in such a way that they will wrap as necessary depending on the width of the browser window (liquid design). With the last requirement we have stepped out of the realm of tables, and into the realm of CSS.
Let’s look at this step-by-step. The first requirement is that the thumbnails have their caption centered beneath them. This is relatively straightforward: in your HTML place your image, followed by a break, and then put your caption in a paragraph that is aligned to the center (via CSS).
Next we want to line up these image/caption pairs across the browser window. Using tables for layout, each of these image/caption pairs would go into a separate TD. Using CSS we need to put them into a separate DIV. To get them to line up horizontally across the window we use CSS to FLOAT each of these DIVs to the left.
Here is what the CSS might look like at this point:
div.float { float: left; } div.float p { text-align: center; }
And the HTML:
<div class="float"> <img src="image1.gif" width="100" height="100" alt="image 1" /><br /> <p>caption 1</p> </div><div class="float"> <img src="image2.gif" width="100" height="100" alt="image 2" /><br /> <p>caption 2</p> </div><div class="float"> <img src="image3.gif" width="100" height="100" alt="image 3" /><br /> <p>caption 3</p> </div>
And here is what it looks like in the browser:
caption 1
caption 2
caption 3
The next requirement can only be solved using CSS. We want the image/caption pairs to wrap if there are more than will fit in the browser window. FLOATing the DIVs to the left has already solved this problem. If we repeat those sample thumbnails a couple of times, they will wrap in the browser window:
caption 1
caption 2
caption 3
caption 1
caption 2
caption 3
Now, suppose you had more than one category of thumbnails you wished to display on your page, and you want to group them visually, with a background and/or border. Simply enclose them in a container DIV:
div.container { border: 2px dashed #333; background-color: #ffe; }
However when we do this we run into a problem. When you float an element with CSS, it no longer takes up any “space” and the background and border show up above the images instead of surrounding them. We need to put some content other than the floated DIVs into the container DIV. Like a spacer DIV:
div.spacer { clear: both; }
Now the following HTML (note that there are spacer DIVs at the top and bottom of the container DIV):
<div class="container"> <div class="spacer"> </div><div class="float"> <img src="image1.gif" width="100" height="100" alt="image 1" /><br /> <p>caption 1</p> </div><div class="float"> <img src="image2.gif" width="100" height="100" alt="image 2" /><br /> <p>caption 2</p> </div><div class="float"> <img src="image3.gif" width="100" height="100" alt="image 3" /><br /> <p>caption 3</p> </div><div class="spacer"> </div></div>
...gives us what is shown next:
caption 1
caption 2
caption 3
So now we have a bunch of nested DIVs. How is this any better than the nested tables they replace? The answer lies in the way the tag was intended to be used. DIVs imply a logical, or structural grouping. Even when they are nested they remain structural markup. In our example we grouped images with their captions (first level), and then grouped these image/caption pairs with similar image/caption pairs (second level). These are both examples of structural grouping that is handled quite well by the DIV tag.
However, tables imply a relationship between column and/or row headers, and the data in the table cells. When we use them for layout, we lose the structural semantics of a table. And we are back to using HTML for layout. Nesting tables only compounds the problem.
Another common use for table layout is lining up FORM elements with their labels. While it could be argued that this is an appropriate use of TABLEs, the CSS technique that I describe below will prove to be useful for other, similar layout needs, as we will see.
A typical layout for FORMs has the labels on the left, flush right, with the form elements on the right, flush left, with everything meeting in the middle:
The form above was created without the use of TABLEs. Once again we are using FLOAT to accomplish the positioning. The trick is to create a DIV that works like the TABLE row that we are used to using. Then we’ll create two SPANs: one for the labels and the other for the form elements. Float the label SPAN left and the form element SPAN right. For the label SPAN, align the text right, and give the form element SPAN left-aligned text.
The CSS looks like this:
div.row { clear: both; padding-top: 10px; }div.row span.label { float: left; width: 100px; text-align: right; }div.row span.formw { float: right; width: 335px; text-align: left; }
The CSS above also gives widths for the SPANs. These can be absolute values like the example, or percentages that add up to 100% or slightly less, depending on padding and borders (and the box model you are designing for). In the example I have wrapped the form in another DIV to give it a border and a background.
The example HTML looks like:
<div style="width: 350px; background-color: #cc9; border: 1px dotted #333; padding: 5px; margin: 0px auto";> <form> <div class="row"> <span class="label">Name:</span><span class="formw"><input type="text" size="25" /></span> </div> <div class="row"> <span class="label">Age:</span><span class="formw"><input type="text" size="25" /></span> </div> <div class="row"> <span class="label">Shoe size:</span><span class="formw"><input type="text" size="25" /></span> </div> <div class="row"> <span class="label">Comments:</span><span class="formw"> <textarea cols="25" rows="8"> Go ahead - write something… </textarea> </span> </div> <div class="spacer"> </div> </form></div>
You may have noticed part of the STYLE attribute for the containing DIV above contained the following: margin: 0px auto;
. This results in the 400 pixel DIV being centered in standards compliant browsers. Some browsers, like IE5.x for Windows ignore this, but will (incorrectly) center DIVs that have a TEXT-ALIGN of center. To center DIVs in these browsers you can wrap a DIV with TEXT-ALIGN set to center, around another DIV that sets MARGIN to auto (and TEXT-ALIGN to left so text will align correctly). See Rob Chandanais’ Layout Reservoir for this and other centering technniques.
A similar layout that is typically solved with tables is essentially the opposite of the above. Instead of meeting in the middle, you might want to place two elements at opposite sides of the browser window. This might be a case where you have a small logo that you want at the top right corner of your page, and some navigational elements at the top left:
Here we will use the same DIV.ROW, but different SPANs than we did for aligning the FORM elements with their labels. The SPAN on the left will float left, and contain left-aligned text. The SPAN on the right will float right and contain right-aligned text.
CSS:
div.row span.left { float: left; text-align: left; font-weight: bold; color: #fff; width: 49%; }div.row span.right { float: right; text-align: right; font-weight: bold; color: #fff; width: 49%; }
HTML:
<div style= "width: 90%; background-color: #666; border: 1px solid #333; padding: 0px; margin: 0px auto;"> <div class="spacer"></div><div class="row"><span class="left"> Home > Products</span> <span class="right"> [logo]</span></div><div class="spacer"></div></div>
The ACRONYM and ABBR tags are useful, if little used, tags that use the TITLE attribute to expand the acronym or abbreviation. Most current browsers do nothing to alert your site visitors that there is anything behind the words on the page that can help them make sense of the abbreviation or acronym. Enter CSS.
In your style sheet you can add a bottom border to those tags to draw attention to them on the page. You can also use CSS to change the cursor into a “Help” cursor (usually a question mark) for those browsers that support it. And you aren’t limited to HTML tags. Create a class called .help and use SPANs to give more information about words or phrases that your readers might be confused by.
This CSS:
abbr, acronym, .help { border-bottom: 1px dotted #333; cursor: help; }
... is used in conjunction with the TITLE attribute on an ABBR or ACRONYM tag to create an underline effect that is different from the hyperlink underline. Changing the cursor to “help” implies that the word is not clickable, and the TITLE attribute expands the abbreviation or acronym. I first saw this done at a site by Sander Tekelenburg.
I first read about changing the display of a list to inline in Bos and Lie’s Cascading Style Sheets. I first saw it put to use in Christopher Schmitt’s Babble List website. The trick takes a list and makes it display inline, or horizontally. So instead of:
...you get:
Adding some padding and a border effect gives:
CSS:
li.inline { display: inline; padding-left: 3px; padding-right: 7px; border-right: 1px dotted #066; }li.last { display: inline; padding-left: 3px; padding-right: 3px; border-right: 0px; }
HTML:
<ul> <li class="inline">Item one</li><li class="inline">Item two</li><li class="last">Item three</li> </ul>
It is my hope that in sharing these tips, tricks and techniques, you will be encouraged to use more CSS Layout in your web work, and that you will continue to discover, and share, new tips, tricks and techniques with others.